package com.vipertrans.util;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.vipertrans.token.CharToken;
import com.vipertrans.token.EOF;
import com.vipertrans.token.Ident;
import com.vipertrans.token.Keyword;
import com.vipertrans.token.Other;
import com.vipertrans.token.SpecialSymbol;
import com.vipertrans.token.StringToken;
import com.vipertrans.token.Token;
import com.vipertrans.token.Number;

public class Scanner {

    BufferedReader reader;

    private char ch;
    private int line;
    private int column;

    Scanner(BufferedReader reader) {

	this.reader = reader;
	line = 1;
	column = 0;

	readNextChar();
    }

    /**
     * Reads next char Check if is end of line or fail Save the read char
     */
    public void readNextChar() {

	try {
	    int ch1 = reader.read();
	    ch = (ch1 < 0) ? TConsts.EOF : (char) ch1;
	    if (ch == TConsts.CR) {
		line++;
		column = 0;
	    } else if (ch == TConsts.LF) {
		column = 0;
	    }
	} catch (IOException ex) {
	    Logger.getLogger(Scanner.class.getName()).log(Level.SEVERE, null, ex);
	    System.err.println("Error in Scaner.readNextChar()");
	    ex.printStackTrace();
	}

    }

    public char unEscape(char c) {

	switch (c) {
	case 't':
	    return '\t';
	case 'n':
	    return '\n';
	case 'r':
	    return '\r';
	case 'f':
	    return '\f';
	case '\'':
	    return '\'';
	case '"':
	    return '\"';
	case '0':
	    return '\0';
	case TConsts.ESC:
	    return TConsts.ESC;
	default:
	    return c;
	}
    }

    private boolean isLetter() {
	if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch == '_') {
	    return true;
	} else {
	    return false;
	}
    }
    
    private boolean isDigit() {
	if(ch>='0' && ch<='9') {
	    return true;
	} else {
	    return false;
	}
    }
    
    private boolean isSpace() {
	if(ch == ' ' || ch == '\t' || ch == TConsts.CR|| ch == TConsts.LF) {
	    return true;
	} else {
	    return false;
	}
    }

    /**
     * Infinite loop Checking for known types of tokens; Constructing the object
     * token Tuning of its characteristics
     * 
     * To read next char use method <b>ReadNextChar()</b>
     * 
     * @return the object and exit the loop
     */
    public Token Next() {
	int startColumn;
	int startLine;

	while (true) {
	    startColumn = column;
	    startLine = line;

	    //Ident - checks for Ident = Letter {Letter | Digit}
	    if(isLetter()) {
		StringBuilder sb  = new StringBuilder();
		
		//Makes String while Ident {Letter | Digit} returns true
		while(isLetter() || isDigit()) {
		    
		    sb.append(ch);
		    readNextChar();
		}
		
		//the word that we was build
		String word = sb.toString();
		
		if(TConsts.KEYWORD.contains(" " + word + " ")) {
		    return new Keyword(startLine, startColumn, word);
		}
		
		return new Ident(startLine, startColumn, word);
		
	    } else if(isDigit()) {
		
		StringBuilder sb = new StringBuilder();
		
		while(isDigit()) {
		    sb.append(ch);
		    readNextChar();
		}
		
		return new Number(startLine, startColumn, Integer.parseInt(sb.toString()));
		
	    }  else if (ch == '\'') {
		
		readNextChar();
		char ch1 = ch;
		if(ch == TConsts.ESC) {
		    readNextChar();
		    ch1 = unEscape(ch1);
		}
		
		readNextChar();
		
		if(ch == '\'') {
		    
		    readNextChar();
		    return new CharToken(startLine, startColumn, ch1);
		} else {
		    try {
			throw new Exception("Expects close sine \"'\" of the line:{" + startLine + "} clumn{" + startColumn + "}" );
		    } catch (Exception e) {
			Logger.getLogger(Scanner.class.getName()).log(Level.SEVERE, null, e);
			e.printStackTrace();
		    }
		}
		
	    } else if (ch == '"') {
		
		StringBuilder sb  = new StringBuilder();
		readNextChar();
		
		while(ch !='"' && ch != TConsts.EOF) {
		    
		    char ch1 = ch;
		    
		    if(ch == TConsts.ESC) {
			readNextChar();
			ch1 = unEscape(ch);
		    }
		    sb.append(ch1);
		    readNextChar();
		}
		readNextChar();
		return new StringToken(startLine, startColumn, sb.toString());
		
	    } else if(TConsts.SPECIAL_SIMBOL.contains(" "+ ((Character)ch).toString()+ " ")) {
		
		char ch1 = ch;
		readNextChar();
		
		return  new SpecialSymbol(startLine, startColumn, ((Character) ch1).toString());
		
	    } else if( isSpace() ) {
		
		readNextChar();
		continue;
		
	    } else if(ch == '/') {
		
		char ch1 = ch;
		readNextChar();
		
		if(ch == '/') {
		    
		    while(ch != TConsts.CR && ch != TConsts.LF) {
			readNextChar();
		    }
		    readNextChar();
		    
		} else if (ch == '*') {
		    
		    readNextChar();
		    do {
			while(ch != '*' && ch != TConsts.EOF) {
			    readNextChar();
			}
			readNextChar();
		    } while( ch != '/' && ch != TConsts.EOF);
		    
		    readNextChar();
		    
		} else {
		    
		    return new SpecialSymbol(startLine, startColumn, ((Character) ch1).toString());
		}
		continue;
		
	    } else if (ch == TConsts.EOF) {
		
		return new EOF(startLine, startColumn);
		
	    } else {
		
		String s = ((Character) ch).toString();
		readNextChar();
		return new Other(startLine, startColumn, s);
	    }
	    
	}

    }

}
